package org.simplespringframework.mvc.processor.impl;

import lombok.extern.slf4j.Slf4j;
import org.simplespringframework.BeanContainer;
import org.simplespringframework.mvc.RequestProcessorChain;
import org.simplespringframework.mvc.annotation.RequestMapping;
import org.simplespringframework.mvc.annotation.RequestParam;
import org.simplespringframework.mvc.annotation.ResponseBody;
import org.simplespringframework.mvc.processor.RequestProcessor;
import org.simplespringframework.mvc.render.ResultRender;
import org.simplespringframework.mvc.render.impl.JsonResultRender;
import org.simplespringframework.mvc.render.impl.ResourceNotFoundResultRender;
import org.simplespringframework.mvc.render.impl.ViewResultRender;
import org.simplespringframework.mvc.type.ControllerMethod;
import org.simplespringframework.mvc.type.RequestPathInfo;
import org.simplespringframework.util.ConverterUtil;
import org.simplespringframework.util.ValidationUtil;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @BelongsProject: simpleframework
 * @BelongsPackage: org.simplespringframework.mvc.processor.impl
 * @Author: xshang
 * @CreateTime: 2023-06-28  16:23
 * @Description: controller请求处理器
 * 针对特定请求选择匹配的Controller方法进行处理
 * 解析请求里的参数及其对应的值，并赋值给Controller方法的参数
 * 选择合适的Render，为后续请求处理结果的渲染做准备
 * @Version: 1.0
 */

@Slf4j
public class ControllerRequestProcessor implements RequestProcessor {

    // IOC容器
    private final BeanContainer beanContainer;

    // 请求和controller方法的映射集合
    private final Map<RequestPathInfo, ControllerMethod> pathControllerMethodMap = new ConcurrentHashMap<>();


    /**
     * 依靠容器的能力，建立起请求路径，请求方法与controller方法实例的映射
     */
    public ControllerRequestProcessor() {
        this.beanContainer = BeanContainer.getInstance();
        // 获取所有被RequestMapping注解标记的class集合
        Set<Class<?>> requestMappingSet = this.beanContainer.getClassesByAnnotation(RequestMapping.class);
        // 解析 并 建立请求路径方法与controller方法实例的映射关系,同时将映射关系保存到pathControllerMethodMap
        initPathControllerMethodMap(requestMappingSet);
    }


    private void initPathControllerMethodMap(Set<Class<?>> requestMappingSet) {
        if (ValidationUtil.isEmpty(requestMappingSet)) {
            return;
        }
        // 1.遍历所有被@RequestMapping标记的类，获取类上面该注解的属性值作为一级路径
        for (Class<?> requestMappingClass : requestMappingSet) {

            RequestMapping requestMapping = requestMappingClass.getAnnotation(RequestMapping.class);
            String basePath = requestMapping.value();
            // 如果路径不是 / 开头  就加上 /
            if (!basePath.startsWith("/")) {
                basePath = "/" + basePath;
            }
            // 2.遍历类里所有被@RequestMapping标记的方法，获取方法上面该注解的属性值，作为二级路径
            Method[] methods = requestMappingClass.getDeclaredMethods();
            if (ValidationUtil.isEmpty(methods)) {
                // 如果为空就跳过当前类
                continue;
            }
            // 解析方法上的RequestMapping注解 获取路径值 与 类上的路径拼接出一个完整的路径
            for (Method method : methods) {
                if (method.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
                    String methodPath = methodRequestMapping.value();
                    if (!methodPath.startsWith("/")) {
                        methodPath = "/" + methodPath;
                    }
                    String url = basePath + methodPath;
                    // 3.解析方法里面被@RequestParam标记的参数
                    //  获取该注解的属性值，作为参数名
                    //  获取被标记的参数的数据类型，建立参数名和参数类型的映射
                    HashMap<String, Class<?>> methodParam = new HashMap<>();
                    // 获取方法上的参数数组列表
                    Parameter[] parameters = method.getParameters();
                    if (!ValidationUtil.isEmpty(parameters)) {
                        for (Parameter parameter : parameters) {
                            RequestParam param = parameter.getAnnotation(RequestParam.class);
                            // 目前暂定为所有方法里面的所有参数都要加上@RequestParam注解
                            if (param == null) {
                                throw new RuntimeException("目前每个参数都需要加上@RequestParam注解");
                            }
                            methodParam.put(param.value(), parameter.getType());
                        }
                    }
                    // 4.将获取到的信息封装成RequestPathInfo实例和ControllerMethod实例，放置到映射表里
                    // 方法上的RequestMapping注解里的method属性获取请求方式
                    String httpMethod = String.valueOf(methodRequestMapping.method());

                    RequestPathInfo requestPathInfo = new RequestPathInfo(httpMethod, url);

                    if (this.pathControllerMethodMap.containsKey(requestPathInfo)) {
                        log.error("这个requestPathInfo已存在,将会替换已存在的requestPathInfo");
                    }
                    ControllerMethod controllerMethod = new ControllerMethod(requestMappingClass, method, methodParam);
                    this.pathControllerMethodMap.put(requestPathInfo, controllerMethod);
                }
            }
        }
    }


    @Override
    public boolean process(RequestProcessorChain requestProcessorChain) throws Exception {
        // 1.解析HttpServletRequest的请求方法，请求路径，获取对应的ControllerMethod实例
        String method = requestProcessorChain.getRequestMethod();
        String path = requestProcessorChain.getRequestPath();
        ControllerMethod controllerMethod = this.pathControllerMethodMap.get(new RequestPathInfo(method, path));
        if (controllerMethod == null) {
            requestProcessorChain.setResultRender(new ResourceNotFoundResultRender(method, path));
            return false;
        }
        // 2.解析请求参数，并传递给获取到的ControllerMethod实例去执行
        Object result = invokeControllerMethod(controllerMethod, requestProcessorChain.getRequest());
        // 3.根据解析的结果，选择对应的render进行渲染
        setResultRender(result, controllerMethod, requestProcessorChain);
        return true;
    }

    /**
     * 根据解析的结果，选择对应的render进行渲染
     *
     * @param result                result
     * @param controllerMethod      controllerMethod
     * @param requestProcessorChain requestProcessorChain
     */
    private void setResultRender(Object result, ControllerMethod controllerMethod, RequestProcessorChain requestProcessorChain) {
        if (result == null) {
            return;
        }
        ResultRender resultRender;
        boolean isJson = controllerMethod.getInvokeMethod().isAnnotationPresent(ResponseBody.class);
        if (isJson) {
            resultRender = new JsonResultRender(result);
        } else {
            resultRender = new ViewResultRender(result);
        }
        requestProcessorChain.setResultRender(resultRender);
    }

    /**
     * 解析请求参数，并传递给获取到的ControllerMethod实例去执行
     *
     * @param controllerMethod controllerMethod
     * @param request          request
     * @return Object
     */
    private Object invokeControllerMethod(ControllerMethod controllerMethod, HttpServletRequest request) {
        // 1.从请求里获取GET或者POST的参数名及其对应的值
        Map<String, String> requestParamMap = new HashMap<>();
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (String key : parameterMap.keySet()) {
            String[] value = parameterMap.get(key);
            if (!ValidationUtil.isEmpty(value)) {
                // 只支持一个参数对应一个值
                requestParamMap.put(key, value[0]);
            }
        }
        // 2.根据获取到的请求参数名及其对应的值，以及controllerMethod里面的参数和类型的映射关系，去实例化出方法对应的参数
        List<Object> methodParams = new ArrayList<>();
        Map<String, Class<?>> methodParameMap = controllerMethod.getMethodParameters();
        for (String paramName : methodParameMap.keySet()) {
            Class<?> type = methodParameMap.get(paramName);
            // 请求参数对应的值
            String requestValue = requestParamMap.get(paramName);
            Object value;
            // 目前只支持String类型，以及基础类型char，int，boolean，short，double，long，float 以及他们的包装类型
            if (requestValue == null) {
                // 将请求里的参数值转成适配于参数类型的空值
                value = ConverterUtil.primitiveNull(type);
            } else {
                // requestValue不为空
                value = ConverterUtil.convert(type, requestValue);
            }
            methodParams.add(value);
        }
        // 3.执行controller里面对应的方法并返回结果
        Object controller = this.beanContainer.getBean(controllerMethod.getControllerClass());
        Method invokeMethod = controllerMethod.getInvokeMethod();
        invokeMethod.setAccessible(true);
        Object result;
        try {
            if (methodParams.size() == 0) {
                result = invokeMethod.invoke(controller);
            } else {
                result = invokeMethod.invoke(controller, methodParams.toArray());
            }
        } catch (InvocationTargetException e) {
            // 如果是调用异常，需要通过e.getTargetException() 去执行方法抛出的异常
            throw new RuntimeException(e.getTargetException());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return result;
    }
}
